// SPDX-License-Identifier: Mulan PSL v2
/*
 * Copyright (c) 2025 Huawei Technologies Co., Ltd.
 * This software is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *         http://license.coscl.org.cn/MulanPSL2
 *
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

#[cfg(test)]
mod tests {
    use crate::{
        RecordBlock, RecordPayload, RecorderConfig, RecorderHeader, RequestLogger, RequestPlayer,
        RequestPlayerBuilder, FOOTER_MAGIC, RECORD_FILE_MAGIC,
    };

    #[test]
    fn test_protobuf_types() {
        // test RecorderHeader
        let header = RecorderHeader::new(); // Use new() instead of default()
        assert_eq!(header.magic, RECORD_FILE_MAGIC);
        assert!(header.version.is_some());
        assert!(header.context.is_some());

        // Test RecordBlock
        let block = RecordBlock::default();
        assert_eq!(block.block_id, 0);
        assert_eq!(block.timestamp_ns, 0);
        assert_eq!(block.thread_id, 0);
        assert!(block.payload.is_none());

        println!("✅ Protobuf types basic functionality test passed");
    }

    /// Test RecordBlock creation and validation
    #[test]
    fn test_record_block_creation() {
        // Create test payload
        let test_data = b"test_request_data";
        let payload = RecordPayload {
            length: test_data.len() as u64,
            crc32_checksum: crc32fast::hash(test_data),
            request_data: test_data.to_vec(),
        };

        // Create record block
        let block = RecordBlock {
            block_id: 1,
            timestamp_ns: 123456789,
            thread_id: 999,
            payload: Some(payload),
        };

        // Verify data
        assert_eq!(block.block_id, 1);
        assert_eq!(block.timestamp_ns, 123456789);
        assert_eq!(block.thread_id, 999);
        assert!(block.payload.is_some());

        let payload = block.payload.as_ref().unwrap();
        assert_eq!(payload.length, test_data.len() as u64);
        assert_eq!(
            <Vec<u8> as AsRef<[u8]>>::as_ref(&payload.request_data),
            test_data
        );

        println!("✅ RecordBlock creation test passed");
    }

    /// Test CRC32 checksum calculation
    #[test]
    fn test_crc32_checksum() {
        let test_data = b"Hello, World!";
        let checksum1 = crc32fast::hash(test_data);
        let checksum2 = crc32fast::hash(test_data);

        // Same data should have the same checksum
        assert_eq!(checksum1, checksum2);

        // Different data should have different checksums
        let other_data = b"Hello, Rust!";
        let checksum3 = crc32fast::hash(other_data);
        assert_ne!(checksum1, checksum3);

        println!("✅ CRC32 checksum test passed");
    }

    /// Test simplified configuration structure
    #[test]
    fn test_recorder_config() {
        let default_config = RecorderConfig::default();
        assert_eq!(default_config.output_path.to_str().unwrap(), "requests.rec");
        assert_eq!(default_config.block_size, 128);
        assert_eq!(default_config.max_file_size, 1024 * 1024 * 1024);
        assert!(default_config.checksum_enabled);

        // Test custom configuration
        let mut custom_config = default_config.clone();
        custom_config.block_size = 128;
        custom_config.compression = true;

        assert_eq!(custom_config.block_size, 128);
        assert!(custom_config.compression);

        println!("✅ RecorderConfig test passed");
    }

    /// Test timestamp generation
    #[test]
    fn test_timestamp_generation() {
        use std::time::{SystemTime, UNIX_EPOCH};

        let timestamp1 = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_nanos() as u64;

        std::thread::sleep(std::time::Duration::from_millis(1));

        let timestamp2 = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_nanos() as u64;

        // Timestamps should be increasing
        assert!(timestamp2 > timestamp1);

        println!("✅ Timestamp generation test passed");
    }

    #[test]
    fn test_request_logger() {
        let config = RecorderConfig::default();
        // Create a RequestLogger instance
        let mut logger = RequestLogger::new(config).unwrap();

        // Log a test request
        let test_data = b"test_request_data1";
        logger.log_request_serialized(test_data).unwrap();

        // Log a test request
        let test_data = b"test_request_data2";
        logger.log_request_serialized(test_data).unwrap();

        // Log a test request
        let test_data = b"test_request_data3";
        logger.log_request_serialized(test_data).unwrap();

        // Log a test request
        let test_data = b"test_request_data4";
        logger.log_request_serialized(test_data).unwrap();

        // Log a test request
        let test_data = b"A".repeat(1024);
        logger.log_request_serialized(&test_data).unwrap();

        logger.close().unwrap();

        println!("✅ RequestLogger test passed");
    }

    #[test]
    fn test_request_player() {
        // Create a RequestPlayer instance
        let mut player = RequestPlayer::from_file("requests.rec").unwrap();

        // Play back the test request
        while let Some(Ok(block)) = player.next_block().transpose() {
            if let Some(payload) = block.payload {
                // request_data
                let request_data = payload.request_data;

                // Validate CRC32 checksum
                let expected_crc = payload.crc32_checksum;
                let actual_crc = crc32fast::hash(&request_data);

                if expected_crc == actual_crc {
                    println!(
                        "Data length: {}; data: {:?}; str: {}",
                        payload.length,
                        request_data,
                        String::from_utf8_lossy(&request_data)
                    );
                }
            }
        }
        println!("✅ RequestPlayer test passed");
    }

    #[test]
    fn test_request_player_builder() {
        // Create a RequestPlayer instance using RequestPlayerBuilder
        let mut player = RequestPlayerBuilder::new()
            .file_path("requests.rec")
            .build()
            .unwrap();

        let mut count = 0;
        while let Some(Ok(_)) = player.next_block().transpose() {
            count += 1;
        }

        assert_eq!(count, 5, "Should have 5 records");
    }

    #[test]
    fn test_request_player_header() {
        let player = RequestPlayer::from_file("requests.rec").unwrap();
        let header = player.header();
        assert_eq!(header.magic, RECORD_FILE_MAGIC);

        let version = header.version.unwrap();
        println!("Version: {:#?}", version);

        assert!(header.version.is_some(), "Version should be set");
        assert!(header.context.is_some(), "Context should be set");
    }

    #[test]
    fn test_request_player_footer() {
        let player = RequestPlayer::from_file("requests.rec").unwrap();

        let footer = player.footer();

        let footerstart = player.position();

        assert_eq!(footer.blocks, 5);
        assert_eq!(footer.sha256_digest, [0xff; 32]);
        assert_eq!(footer.magic, FOOTER_MAGIC);
        assert_eq!(footerstart, 0);
    }

    /// Performance comparison: sync vs async mode
    #[test]
    fn test_async_performance_benefit() {
        use std::time::Instant;
        use tempfile::NamedTempFile;

        let request_count = 1000000u32;
        let test_requests: Vec<[u8; 4]> = (0..request_count).map(|i| i.to_be_bytes()).collect();
        // Test sync mode
        let temp_file_sync = NamedTempFile::new().expect("Failed to create temp file");
        let sync_start = Instant::now();
        {
            let config = RecorderConfig {
                output_path: temp_file_sync.path().to_path_buf(),
                block_size: 1024,
                ..Default::default()
            };
            let mut logger =
                RequestLogger::new_with_async(config, false).expect("Failed to create sync logger");
            println!("sync mode: started logging\n");
            for request in &test_requests {
                logger
                    .log_request_serialized(request)
                    .expect("Failed to log");
            }
            println!("sync mode: finished logging and closing\n");
            logger.close().expect("Failed to close");
        }
        let sync_duration = sync_start.elapsed();
        // Test async mode
        let temp_file_async = NamedTempFile::new().expect("Failed to create temp file");
        let async_start = Instant::now();
        {
            let config = RecorderConfig {
                output_path: temp_file_async.path().to_path_buf(),
                block_size: 1024,
                ..Default::default()
            };
            let mut logger = RequestLogger::new(config).expect("Failed to create async logger");
            println!("async mode: started logging\n");
            for request in &test_requests {
                logger
                    .log_request_serialized(request)
                    .expect("Failed to log");
            }
            println!("async mode: finished logging and closing\n");
            logger.close().expect("Failed to close");
        }
        let async_duration = async_start.elapsed();
        println!("\n📊 Performance Comparison ({}  requests):", request_count);
        println!("  Sync mode:  {:?}", sync_duration);
        println!("  Async mode: {:?}", async_duration);
        println!(
            "  Speedup:    {:.2}x",
            sync_duration.as_secs_f64() / async_duration.as_secs_f64()
        );
        println!("✅  Performance test completed");
    }
    /// Performance comparison: sync vs async mode
    #[test]
    fn test_async_log_and_replay_performance() {
        use std::path::PathBuf;
        use std::time::Instant;

        let request_count = 1000000u32;
        let test_requests: Vec<[u8; 4]> = (0..request_count).map(|i| i.to_be_bytes()).collect();
        // Test async mode
        let temp_file_async = PathBuf::from("lager_requests.rec");
        let async_start_log = Instant::now();
        {
            let config = RecorderConfig {
                output_path: temp_file_async.to_path_buf(),
                block_size: 128,
                ..Default::default()
            };
            println!("async mode: output_path: {}\n", temp_file_async.display());
            let mut logger = RequestLogger::new(config).expect("Failed to create async logger");
            println!("async mode: started logging {}\n", test_requests.len());
            for request in &test_requests {
                logger
                    .log_request_serialized(request)
                    .expect("Failed to log");
            }
            println!("async mode: finished logging and closing\n");
            logger.close().expect("Failed to close");
            // logger.flush().expect("Failed to flush");
        }
        let async_log_duration = async_start_log.elapsed();
        let mut player =
            RequestPlayer::from_file(temp_file_async).expect("Failed to create player");

        // check footer
        let footer = player.footer();
        let start = player.position();
        assert_eq!(footer.blocks, request_count as u64);
        assert_eq!(footer.sha256_digest, [0xff; 32]);
        assert_eq!(start, 0);

        let mut i = 0;
        let async_start_player = Instant::now();
        while let Some(Ok(block)) = player.next_block().transpose() {
            let request = block.payload.expect("Failed to rebuild request");
            // println!("{:?}", request.request_data);
            let arr: [u8; 4] = request.request_data.try_into().unwrap();
            let n = u32::from_be_bytes(arr);
            assert_eq!(n, i);

            i += 1;
        }
        let async_player_duration = async_start_player.elapsed();
        println!("\n📊 Performance Comparison ({}  requests):", request_count);
        println!("played {} requests\n", i);
        println!(
            "  Async mode: async_log_duration {:?}, async_player_duration {:?}",
            async_log_duration, async_player_duration
        );
        println!("✅  Performance test completed");
    }
}
